June 2019

Agenda

Animation: When and Why?

Introducing the Tools

Hands-On Tutorial

So You’re Thinking About Animating

What are some reasons to animate a plot?

Bad Reasons

  • It’s neat
  • I want to show off
  • One of my dimensions is time

Good Reasons

  • I have four or more meaningful data dimensions
  • I want to display many levels of a single dimension

Also: If you need to see all the data points at the same time DON’T use animation

Examples of Good Cases

  • data changing over a long period of time
  • groups of datapoints varying systematically

Ask Yourself

  • What information do you want the audience to take away?

The Tools

Packages to Use

library(ggplot2) # make plots
library(gganimate) # animate the plots
library(gifski) # render gifs
library(transformr) # insert smooth transformations

Key Concepts

Frame

  • Animations are a series of single images (frames) strung together

State

  • The current grouping variable being shown. It may be one frame, or a single state representation can stay visible for multiple frames

Transition

  • How the visual rendering moves from state to state

Enter/Exit

  • How datapoints with no predecessor or follower are handled

Tutorial

Project Overview

Load Data

Grab dataset of displaced persons living in South Africa

X_date_year X_country_residence X_country_origin X_population_type X_affected
1993 South Africa Mozambique Refugees (incl. refugee-like situations) 250000
1994 South Africa Angola Refugees (incl. refugee-like situations) 581
1994 South Africa Dem. Rep. of the Congo Refugees (incl. refugee-like situations) 808
1994 South Africa Ethiopia Refugees (incl. refugee-like situations) 66

Mutate Data

Select the top 10 countries of origin for each year, apply some filters.

library(magrittr)
library(dplyr)

plotDT <- dtset %>%
  filter(X_country_origin != "Various/Unknown") %>%
  filter(X_population_type == "Refugees (incl. refugee-like situations)") %>%
  filter(X_date_year > 1995) %>%
  group_by(X_date_year) %>%
  mutate(rank = rank(-X_affected, ties.method = "first") * 1) %>%
  ungroup() %>%
  filter(rank <= 10) %>%
  data.frame()

Our New Data

X_date_year X_country_residence X_country_origin X_population_type X_affected rank state_time
1996 South Africa Angola Refugees (incl. refugee-like situations) 3876 1 1
1996 South Africa Bangladesh Refugees (incl. refugee-like situations) 452 9 1
1996 South Africa China Refugees (incl. refugee-like situations) 469 8 1
1996 South Africa Dem. Rep. of the Congo Refugees (incl. refugee-like situations) 2505 3 1

Grouped Bar Plot - Code

Group by country, bar height is affected persons

baseplot1 <- ggplot(plotDT, 
aes(X_date_year, 
    group = X_country_origin, 
    fill = as.factor(X_country_origin), 
    color = as.factor(X_country_origin)))+
theme_bw()+
theme(legend.position = "bottom")+
geom_bar(aes(y = X_affected), stat = "identity", position = "dodge")

Grouped Bar Plot - Rendered

Ew. This is not effective.

What if we try to make a race plot instead?

Let’s make this!

Adjustment 1: Flip Axis

baseplot2 <- baseplot1 +
    coord_flip(clip = "off", expand = FALSE, ylim = c(0, 50000))

Flipped Axis - Render

Adjustment 2: Stop the bar grouping (dodge), switch axis to country from year

Can’t use the same stub now.

baseplot3 <- ggplot(plotDT, 
aes(x=rank, 
    group = X_country_origin, 
    fill = as.factor(X_country_origin), 
    color = as.factor(X_country_origin)))+
theme_bw()+
theme(legend.position = "bottom")+
geom_text(aes(y = 0, label = paste(X_country_origin, " ")), vjust = 0.2, hjust = 1) +
coord_flip(clip = "off", expand = FALSE, ylim = c(0, 50000)) +
geom_bar(aes(y = X_affected), stat = "identity", position = "identity")

Stop Grouping - Render

Check in: baseplot2 vs baseplot3

Left side: grouped bar, sorted by year
Right side: not grouped bar, sorted by rank

Adjustment 3: Adjust margins, fix axis text, drop legend

If your stub has a theme() segment, applying a new one will overrule it.

baseplot3 <- baseplot3 + 
theme(legend.position = "none",
    axis.ticks.y = element_blank(),  # These relate to the axes post-flip
    axis.text.y  = element_blank(),  # These relate to the axes post-flip
    axis.title.y = element_blank(),
    plot.margin = margin(1,1,1,5, "cm"))

Drop Legend - Render

Adjustment 4: Prettify Y, reverse X direction

baseplot3 <- baseplot3 +
scale_y_continuous(labels = scales::comma) +
scale_x_reverse()

Reverse X - Render

Now we have the different “frames” all layered on top of each other.

Decision Point: How to Proceed?

We need to present the data on an annual basis in a way that tells us:

  1. What locations refugees come from
    • These may be places with high instability or political turmoil at a given time
  2. How the top locations within a year compare to the other top 10 locations

Faceting?

No.

Faceting?

Definitely not.

Develop the Animation

Basic Version

Literally add one more line of code to your ggplot object.

animp <- baseplot3 +
transition_states(X_date_year)

Basic Animation - Render

It’s nice, but we can do better

  1. Viewer can’t tell what the frames represent
  2. Y axis needs labeling
  3. Bars need clear labels
  4. It’s moving abruptly between frames

Add Dynamic Labels

Solving Problem 1 and 2: Added a descriptive title/label that indicate the year of the frame

animp <- baseplot3 +
  geom_text(aes(y = X_affected, 
    label = as.character(X_affected)), 
    color = "black", vjust = 0.2, hjust = -.5)+
  labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
    , y="Affected Persons")+
  transition_states(X_date_year)

Dynamic Labels - Render

Transition Options: Exit/Enter

Solving Problem 3: how do we want the animation elements to move?

Option: shrink and grow on exit and enter

animp <- baseplot3 +
  geom_text(aes(y = X_affected, 
    label = as.character(X_affected)), 
    color = "black", vjust = 0.2, hjust = -.5)+
  labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
    , y="Affected Persons")+
  transition_states(X_date_year)+
  enter_grow() +
  exit_shrink()

Exit/Enter - Render

It’s interesting, but probably not serving the project objectives

Transition Options: Easing

Solving Problem 3: how do we want the animation elements to move?

Option: Ease between positions (moving on page, not exiting or entering)

animp <- baseplot3 +
  geom_text(aes(y = X_affected, 
    label = as.character(X_affected)), 
    color = "black", vjust = 0.2, hjust = -.5)+
  labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
  , y="Affected Persons")+
  transition_states(X_date_year)+
  ease_aes('quartic-in-out')

Easing Transitions - Render

Hopefully, the movement feels less abrasive to the eye now

Transition Options: More Energetic

Solving Problem 3: how do we want the animation elements to move?

Option: For fun, let’s try “back” to see a springier approach

animp <- baseplot3 +
  geom_text(aes(y = X_affected, 
    label = as.character(X_affected)), 
    color = "black", vjust = 0.2, hjust = -.5)+
  labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
  , y="Affected Persons")+
  transition_states(X_date_year)+
  ease_aes('back-in-out')

Bouncy Transitions - Render

Feels a little cartoony- interesting, but again perhaps not what we need

Transition Options: Timing

Solving Problem 3: how do we want the animation elements to move?

In addition to entry, exit, and transition easing:

  • Set pace for the states/transitions
  • Ensure no smoothing between the end and restarting
animp <- baseplot3 +
  geom_text(aes(y = X_affected, 
    label = as.character(X_affected)), 
    color = "black", vjust = 0.2, hjust = -.5)+
  labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
    , y="Affected Persons")+
  transition_states(X_date_year,transition_length = 5, 
      state_length = c(rep(.25, 21), 20), wrap = FALSE)+
  ease_aes('linear')+
  enter_fade() +
  exit_fade()

Set Timings - Render

Slower pace feels smoother, and doesn’t insinuate that the last frame and the first flow into each other

Transition States - What Does It Do?

  transition_states(
    X_date_year,
    transition_length = 5, 
    state_length = c(rep(.25, 21), 20),
    wrap = FALSE)+

Assign the frame unit- here we use the year.

Transition length is the period of time we use for change from frame to frame
- slow it down if you want a smooth looking animation

State length is the period of time where the frame stays static.
- here I am making the very last frame stay static longer

Wrap determines whether to apply transition smoothing between the end and restarting

Experiment with these settings to get the look you want!

Render the Final Animation

animate(animp, fps = 10, duration = 20, width = 800, height = 450) 

Save the Animation

anim_save(filename = "final_race_plot2.gif")

Lessons to Take Away

Figure out the in/out transitions that make sense and are not misleading/distracting

Give the eye enough time to understand each state

Make your plot serve the audience, don’t be fancy if it’s not helpful

References